### Магические методы `__iter__ и __next__`

Вы все знаете, как работает функция `range()`.

Она выдает значения арифметической прогрессии, например:

```python
list(range(5))

```

дает последовательность целых чисел от 0 до 4. Перебрать значения объекта range также можно через итератор:

```python
a = iter(range(5))
next(a)
next(a)
…

```

В конце генерируется исключение `StopIteration`.

Так вот, мы можем создать подобный объект, используя магические методы `__iter__ и __next__`.

Давайте это сделаем для объекта frange, который будет выдавать последовательность вещественных чисел арифметической прогрессии.

Для этого :

```python
class FRange:
    def __init__(self, start=0.0, stop=0.0, step=1.0):
        self.start = start - step
        self.stop = stop
        self.step = step

```

Здесь в инициализатор мы передаем начальное значение прогрессии, конечное и шаг изменения. Также формируем свойство `value`, которое будет представлять собой текущее значение для считывания.

Для перебора элементов добавим в этот класс метод, который будет соответствовать магическому методу `__next__`:

```python
    def __next__(self):
        if self.start + self.step <= self.stop:
            self.start += self.step
            return self.start
        else:
            raise StopIteration
```

В этом методе мы увеличиваем значение value на шаг `step` и возвращаем до тех пор, пока не достигли значения stop (не включая его).

При достижении конца генерируем исключение `StopIteration`, ровно так, как это делает объект range.

Сформируем объект этого класса:

```python
fr = FRange(0, 2, 0.5)
# и четыре раза вызовем метод __next__()

print(fr.__next__())
print(fr.__next__())
print(fr.__next__())
print(fr.__next__())

```

Видим четыре значения нашей арифметической прогрессии.

Если вызвать` __next__()` еще раз:

```python
print(fr.__next__())
```

получим исключение `StopIteration`.

В целом получился неплохой учебный пример.

В действительности, благодаря определению магического метода `__next__` в классе FRange, мы можем применять функцию `next()` для перебора значений его объектов:

```python
fr = FRange(0, 2, 0.5)
print(next(fr))
print(next(fr))
print(next(fr))
print(next(fr))
```

Здесь функция `next()` вызывает метод `__next__` и возвращенное им значение, возвращается функцией `next()`.

При этом, в качестве аргумента мы ей передаем экземпляр самого класса. То есть, объект класса выступает в роли итератора. В нашем случае так и задумывалось.

Однако, перебрать объект fr с помощью цикла for не получится:

```python
for x in fr:
    print(x)
```

Появится ошибка, что объект не итерируемый.

Почему? Ведь мы прописали поведение функции next()?

Этого не достаточно. Необходимо еще, чтобы объект возвращал итератор при вызове функции `iter`:

```python
it = iter(fr)
```

Для этого в классе нужно прописать еще один магический метод `__iter__`.

В нашем примере он будет выглядеть, так:

```python
    def __iter__(self):
        self.start = self.start + self.step
        return self
```

Мы здесь устанавливаем начальное значение `value` и возвращаем ссылку на объекта класса, так как этот объект в нашем примере и есть итератор – через него вызывается магический метод `__next__`.

Теперь, после запуска программы у нас не возникает никаких ошибок и цикл for перебирает значения объекта fr. То же самое мы можем сделать и через `next()`:

```python
fr = FRange(0, 2, 0.5)
it = iter(fr)
print(next(it))
print(next(it))
print(next(it))
print(next(it))
```

Как вы помните, цикл for именно так и перебирает итерируемые объекты, сначала неявно вызывает функцию `iter()`, а затем, на каждой итерации – функцию next(), пока не возникнет исключение StopIteration.

Кроме того, благодаря магическому методу `__iter__` мы теперь можем обходить значения объекта fr много раз с самого начала, например:

```python
it = iter(fr)
print(next(it))
print(next(it))
print(next(it))
print(next(it))

for x in fr:
    print(x)

```

Таким образом, сформировали класс `FRange`, который воспринимается как итерируемый объект с возможностью перебора функцией `next()` или циклом for.


``` python
from franges import frange # pip install frange-py
from matplotlib.mlab import frange
from scipy import arange
from numpy import arange
#numpy.arange (start, stop, step)


```